﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace Qreed.Windows
{
    public partial class ScopeTextBox : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ScopeTextBox"/> class.
        /// </summary>
        public ScopeTextBox()
        {
            InitializeComponent();
        }

        private List<TextScope> _scopes = new List<TextScope>();
        /// <summary>
        /// Gets the scopes.
        /// </summary>
        /// <value>The scopes.</value>
        internal List<TextScope> Scopes
        {
            get { return this._scopes; }
        }

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        /// <value>The text.</value>
        /// <returns>
        /// The text associated with this control.
        /// </returns>
        [Browsable(true)]
        public override string Text
        {
            get{return base.Text;}
            set{base.Text = value;}
        }

        private string _regularExpression;
        /// <summary>
        /// Gets or sets the regular expression.
        /// </summary>
        /// <value>The regular expression.</value>
        public string RegularExpression
        {
            get { return this._regularExpression; }
            set 
            { 
                this._regularExpression = value;

                if (RegularExpressionChanged != null)
                    RegularExpressionChanged(this, new EventArgs());
            }
        }

        /// <summary>
        /// Occurs when the regular expression has changed.
        /// </summary>
        public event EventHandler RegularExpressionChanged;

        /// <summary>
        /// Occurs when a scope is beeing created.
        /// </summary>
        public event EventHandler<ScopeTextBoxEventArgs> CreateScope;

        /// <summary>
        /// Occurs when a scope is beeing removed.
        /// </summary>
        public event EventHandler<ScopeTextBoxEventArgs> RemoveScope;

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.TextChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnTextChanged(EventArgs e)
        {
            Controls.Clear();
            Scopes.Clear();

            TextScope scope = new TextScope(this, false, 0, Text.Length, Text, Text);
            Scopes.Add(scope);
            Controls.Add(scope.Control);

            PositionControls();

            UpdateRegularExpression();

            base.OnTextChanged(e);
        }

        internal void OnCreateScopeClick(TextScope textScope, ToolStripMenuItem menuItem, 
                                         string beforeSelectionText, string selectedText, string afterSelectionText)
        {
            // Create the scope for the selection
            TextScope selectionScope = new TextScope(this, true,
                                                     textScope.Offset + beforeSelectionText.Length,
                                                     selectedText.Length,
                                                     selectedText,
                                                     selectedText);

            // Call the CreateScope event and the let the user choose the name of the scope

            ScopeTextBoxEventArgs scopeEventArgs = new ScopeTextBoxEventArgs(selectionScope);

            if (CreateScope != null)
            {
                CreateScope(this, scopeEventArgs);
                if (scopeEventArgs.Cancel) 
                    return;
            }

            Scopes.Add(selectionScope);
            Controls.Add(selectionScope.Control);                                         

            // Remove the scope
            Scopes.Remove(textScope);
            Controls.Remove(textScope.Control);

            // Check if there's anything before the selection which needs a new normal scope
            if (!string.IsNullOrEmpty(beforeSelectionText))
            {
                TextScope beforeScope = new TextScope(this, false, textScope.Offset,
                                                      beforeSelectionText.Length,
                                                      beforeSelectionText,
                                                      beforeSelectionText);

                Scopes.Add(beforeScope);
                Controls.Add(beforeScope.Control);
            }
            
            // Check if there's anything behind the selection that needs a normal scope
            if (!string.IsNullOrEmpty(afterSelectionText))
            {
                TextScope afterScope = new TextScope(this, false, 
                                                     selectionScope.Offset + selectionScope.Length,
                                                     afterSelectionText.Length,
                                                     afterSelectionText,
                                                     afterSelectionText);

                Scopes.Add(afterScope);
                Controls.Add(afterScope.Control);
            }

            Scopes.Sort();
            PositionControls();

            UpdateRegularExpression();
        }

        private void UpdateRegularExpression()
        {
            string expression = "^";

            foreach (TextScope scope in Scopes)
            {
                if (scope.IsScope)
                {
                    expression += "(?<" + scope.Name.Replace(' ', '_') + ">.+?)";
                }
                else
                {
                    expression += Regex.Escape(scope.Text);
                }
            }

            RegularExpression = expression + "$";
        }

        private void PositionControls()
        {
            SuspendLayout();

            int xOffset = 2;

            for (int i = 0; i < _scopes.Count; i++)
            {
                TextScope currentScope = _scopes[i];
                Control control = currentScope.Control;

                if (!currentScope.IsScope)
                {
#if DEBUG
                    control.Location = new Point(xOffset, 2);
#else
                    control.Location = new Point(xOffset, 5);
#endif
                    control.Width = TextRenderer.MeasureText(currentScope.Text, control.Font).Width;
                }
                else
                    control.Location = new Point(xOffset, 2);

                xOffset += control.Width;
            }

            ResumeLayout(false);
        }

        internal void OnScopeRemoveClick(TextScope textScope, TextScope previousScope, TextScope nextScope)
        {
            if (RemoveScope != null)
            {
                ScopeTextBoxEventArgs scopeEventArgs = new ScopeTextBoxEventArgs(textScope);
                RemoveScope(this, scopeEventArgs);
                if (scopeEventArgs.Cancel)
                    return;
            }

            Scopes.Remove(textScope);
            Controls.Remove(textScope.Control);

            // We're only interested in normal text scopes
            if (previousScope != null)
            {
                if (previousScope.IsScope)
                    previousScope = null;
                else
                {
                    Scopes.Remove(previousScope);
                    Controls.Remove(previousScope.Control);
                }
            }

            if (nextScope != null)
            {
                if (nextScope.IsScope)
                    nextScope = null;
                else
                {
                    Scopes.Remove(nextScope);
                    Controls.Remove(nextScope.Control);
                }
            }

            TextScope newScope = MergeScopes(new TextScope[] { previousScope, textScope, nextScope });
            Scopes.Add(newScope);
            Controls.Add(newScope.Control);
            Scopes.Sort();
            PositionControls();
            UpdateRegularExpression();
        }

        private TextScope MergeScopes(TextScope[] textScopes)
        {
            int offset = -1; // (textScopes[0] == null ? 0 : textScopes[0].Offset);
            int length = 0;
            string text = "";
            string displayText = "";

            foreach (TextScope scope in textScopes)
            {
                if (scope == null)
                    continue;

                if (offset == -1)
                    offset = scope.Offset;

                length += scope.Length;
                text += scope.Text;
                displayText += scope.Name;
            }

            return new TextScope(this, false, offset, length, text, displayText);
        }
    }
}
